<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>临时文件传输</title>
  <link rel="stylesheet" href="./index.css">
</head>

<body>
  <div class="instructions">
    <a href="https://gitee.com/zzwuweijun/learning_code/tree/master/_%E9%A1%B9%E7%9B%AE/%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93-%E5%88%86%E7%89%87%E4%BC%A0%E8%BE%93"
      style="text-decoration: none;">【源码】</a>
    这是个临时存储文件服务[文件切片上传]，有效期30天，具体用途，随你了~
  </div>

  <div class="main">
    <div class="inlineBlock">
      <div class="updateConten">
        <div class="updateFileConten">
          <input type="file" multiple class="file" id="updateFile">
          <div class="file-p">点击上传文件 & 拖拽文件上传
            <em id="qiepian">总切片: <em id="zs">0</em> -- 已上传切片: <em id="scs">0</em> </em>
          </div>
        </div>
      </div>
    </div>
    <div class="inlineBlock">
      <div class="showContent">
        <div class="refresh" onclick="getFiles()">
          刷新 <img src="./images/refresh.svg" id="refresh-img" class="">
        </div>
        <div class="file-content" id="files">
          <!-- <div class="file-item">
            <a href="">点击下载文件点击下载文件点击下载文件点击下载文件.txt</a>
            <div class="file-function">
              <a title="删除文件" href="" class="file-del">
                <img src="./images/delete.svg" alt="" class="myAnimation">
              </a>
              <a title="点击下载文件" href="" download class="file-dow">
                <img src="./images/dow.svg" alt="" class="myAnimation">
              </a>
              <a title="复制文件链接" href="javascript:" class="file-copy">
                <img src="./images/copy.svg" alt="" class="myAnimation">
              </a>
            </div>
          </div> -->
        </div>
      </div>
    </div>
  </div>

  <script>
    let zs_ = 0;
    let scs_ = 0;
    zs_Fn = () => { document.getElementById('zs').innerHTML = zs_; }
    scs_Fn = () => { document.getElementById('scs').innerHTML = scs_; }
    qiepian_add = () => { document.getElementById('qiepian').style.display = 'inline'; }
    qiepian_del = () => { document.getElementById('qiepian').style.display = 'none'; }

    // 上传文件
    document.getElementById('updateFile').addEventListener('change', function (event) {
      zs_ = 0, scs_ = 0, qiepian_add();
      var files = event.target.files;
      for (let i = 0; i < files.length; i++) {
        addRotating();

        const file = files[i];
        const chunkSize = 50 * 1024; // 500KB per chunk  
        const chunks = Math.ceil(file.size / chunkSize);
        zs_ += chunks;
        zs_Fn();

        for (let j = 0; j < chunks; j++) {
          const start = j * chunkSize;
          const end = Math.min(file.size, start + chunkSize);
          const chunk = file.slice(start, end);
          // const fileName = file.name.replace(/[\s\t\n\+]/g, '-');
          const fileName = sanitizeFilename(sanitizedAndNoDotStart(file.name))

          const formData = new FormData();
          formData.append('chunk', chunk);
          formData.append('name', fileName);
          formData.append('index', j);
          formData.append('chunks', chunks);
          formData.append('formatTime', format("MMDD_hhmm"));

          // 使用FormData和XHR或Fetch API发送请求，并附带分片的索引和其他信息  
          var xhr = new XMLHttpRequest();
          xhr.open('POST', `/update?index=${j}&chunks=${chunks}&name=${fileName}`, true);
          xhr.upload.onprogress = function (e) { }; // 处理上传进度 
          xhr.onload = function (en) {
            scs_ += 1, scs_Fn(), (scs_ == zs_ ? (getFiles(), setTimeout(() => { qiepian_del() }, 2000)) : '');

            if (xhr.status >= 200 && xhr.status <= 400) {
              // 分片上传成功
              let response = JSON.parse(xhr.response)
              if (response.status == 1) { getFiles(); }
            } else {
              // 处理错误
            }
          };
          xhr.send(formData);
        }
      }
    });

    // 查询所有文件
    function getFiles() {
      addRotating();
      fetch(`/files`, {}).then(response => {
        if (response.status >= 200 && response.status < 300) { // 判断请求是否成功
          return response.json() // 得到文本字符串。
        } else {
          throw new Error(response.statusText);
        }
      }).then(json => {
        document.getElementById("files").innerHTML = '';
        if (json.success) {
          let downloadHref = location.origin + '/files/';
          json.filesList = json.filesList.reverse()
          json.filesList.forEach(element => {
            var oLi = document.createElement("div");
            oLi.classList.add('file-item');
            oLi.setAttribute('data-file', element);
            oLi.innerHTML = `
              <a target="_blank" href="${downloadHref + '/' + element}">${element}</a>
              <div class="file-function">
                 <a title="删除文件" class="file-del myAnimation" onclick="deleteFile(this,'${element}')">
                  <img src="./images/delete.svg" alt="" class="myAnimation">
                </a>
                <a title="下载文件" href="${downloadHref + '/' + element}" download class="file-dow myAnimation" onclick="copyFn(this,'')">
                  <img src="./images/dow.svg">
                </a>
                <a title="复制链接" href="javascript:" class="file-copy myAnimation" onclick="copyFn(this,'${downloadHref + '/' + element}')">
                  <img src="./images/copy.svg" class="myAnimation">
                </a>
              </div>`
            document.getElementById("files").appendChild(oLi).classList.remove('shake');
          });
        }
        removeRotating();
      }).catch(err => {
        removeRotating();
        console.log('Request Failed', err)
      })
    }

    // 删除文件
    function deleteFile(event, fileName) {
      copyFn(event);
      let confirmed = confirm(`确定要删除？\n\n ${fileName}`);
      if (confirmed) {
        fetch(`/dele?delFileName=${fileName}`, {}).then(response => {
          if (response.status >= 200 && response.status < 300) { return response.json() }
          else { throw new Error(response.statusText); }
        }).then(json => {
          if (json.success) { document.querySelector(`[data-file='${fileName}']`).remove() }
          else { alert(json.message); }
        })
      } else {
        console.log("用户选择取消。");
      }
    }

    window.onload = getFiles();

    function addRotating() { document.getElementById("refresh-img").classList.add('rotating'); }
    function removeRotating() { document.getElementById("refresh-img").classList.remove('rotating'); }

    // 点击一个就跳动一下
    function copyFn(event, text) {
      event.classList.add('bounce');
      // 监听动画结束事件  
      event.addEventListener('animationend', function (event) {
        if (event.animationName === 'bounce') { this.classList.remove('bounce'); }
      }, { once: true }); // { once: true } 确保事件监听器只触发一次  

      copyText(text);
    }

    // 复制文本内容
    function copyText(text) {
      let inputDom = document.createElement('input');  // 创建一个input元素
      inputDom.setAttribute('readonly', 'readonly'); // 防止手机上弹出软键盘
      inputDom.value = text; // 给input元素赋值
      document.body.appendChild(inputDom); // 添加到body
      inputDom.select(); //选中input元素的内容
      document.execCommand('Copy'); // 执行浏览器复制命令
      inputDom.style.display = 'none';
      inputDom.remove(); // 移除input元素
    }

    /**
    * 将指定日期时间或当前日期时间转换成指定的格式。
    * @fmt {String} 日期格式
    * @timestamp {Date} 标准时间。选填。
    */
    function format(fmt, timestamp) {
      const date = timestamp ? new Date(timestamp) : new Date()
      const o = {
        "Y+": date.getFullYear(), "M+": date.getMonth() + 1, "D+": date.getDate(), "h+": date.getHours(),
        "m+": date.getMinutes(), "s+": date.getSeconds(), "W": date.getDay()
      }
      for (var k in o) {
        if (new RegExp("(" + k + ")").test(fmt)) {
          fmt = fmt.replace(RegExp.$1, () => {
            if (k === 'W') { const week = ['日', '一', '二', '三', '四', '五', '六']; return week[o[k]]; }
            else if (k === 'Y+' || RegExp.$1.length == 1) { return o[k]; }
            else { return ("00" + o[k]).substr(("" + o[k]).length); }
          })
        }
      }
      return fmt
    }

    function sanitizeFilename(filename) {
      // 定义一个正则表达式，匹配不被允许的字符  
      // 这里包括了Windows中不允许的字符，以及Linux中可能不推荐的字符（如/和\）  
      // 注意：我们没有包括所有控制字符（ASCII 0-31），因为JavaScript的字符串通常不包含这些字符  
      // 也没有特别处理以点开头的文件名，因为这在很多情况下是允许的  
      const invalidCharsRegex = /[<>:"/\\|?*\r\n\&\%\*\?\+]+/gi;

      // 使用_替换所有不被允许的字符  
      return filename.replace(invalidCharsRegex, '_');
    }
    // console.log(sanitizeFilename("file?name<with>bad:chars.txt")); // 输出: file_name_with_bad_chars.txt  

    // 如果需要特别处理以点开头的文件名（尽管这在很多系统中是允许的），可以添加额外的逻辑  
    // 例如，如果要禁止以点开头的文件名，可以添加如下判断：  
    function sanitizedAndNoDotStart(filename) {
      if (filename.startsWith('.')) {
        // 可以选择返回原始文件名、抛出错误或进行其他处理  
        // 这里我们选择添加一个下划线来避免以点开头  
        return '_' + sanitizeFilename(filename.substring(1));
      }
      return sanitizeFilename(filename);
    }
    // console.log(sanitizedAndNoDotStart(".hiddenfile.txt")); // 输出: _hiddenfile.txt
  </script>
</body>

</html>